An Introduction to Continuations

نویسنده

  • Luc Moreau
چکیده

ions in the -calculus introduce a notion of scope, like quanti ers in the predicate calculus. A variable x occurs free in a term of if x does not appear in the scope of a x. Otherwise, x occurs bound . The set of free variables of an expression M , written FV (M), is de ned inductively as follows. De nition .3 (Free variables) FV (c) = fg FV (x) = fxg FV ( x:M) = FV (M) n fxg FV (MN) = FV (M) [ FV (N) A program is a term without free variables. 2 Notice that the set of free variables of an abstraction ( x:M) is given by the set of free variables of its body M without the parameter x. Following Barendregt [1], we consider terms that are equal up to the renaming of their bound variables as equivalent; such equivalent terms are identi ed by the relation . So we can write x:xz y:yz but y:yz 6 y:yu: Furthermore, we adopt Barenregt's \hygienic variable" convention according to which, bound and free variables are chosen to be di erent in a formula. The notation MfN=xg denotes the result of substituting N for the free occurrences of a variable x in an expression M . Substitution is a quite subtle operation. Indeed, not only must the free occurrences of x in M be replaced by N , but also the free variables of N must remain free in MfN=xg. De nition .4 (Substitution) cfN=xg c (1) xfN=xg N (2) yfN=xg y if x 6 y (3) ( y:M)fN=xg y:(MfN=xg) (4) (M1 M2)fN=xg ((M1fN=xg) (M2fN=xg)) (5) Notice that in (4), the \hygienic variable" convention guarantees that y 6 x and y 62 FV (N). 2 requiring the next n arguments. So, the ternary function that multiplies its arguments can be written as the curried function: ( x:( y:( z:(( (( x) y)) z)))); which can be written after simpli cation as xyz:( ( x y) z): 3 Example .5 1. The substitution of y in ( x:x y) by 5 is straightforward : ( x:x y)f5=yg ( x:x 5): 2. The substitution of x in ( x:x y) by 5 yields ( x:x y) because x has no free occurrence in ( x:x y). ( x:x y)f5=xg ( x:x y): 3. The substitution of y in ( x:x y) by x requires renaming the bound parameter in order to have x free in the resulting term: ( x:x y)fx=yg ( z:z y)fx=yg ( z:z x): 2 Plotkin [29] designed a lambda-calculus with a call-by-value parameter passing technique to model functional languages with the same argument-passing strategy: this calculus is called the call-by-value lambda-calculus, or v-calculus for short. Let us introduce it below. Terms in the v-calculus are the same as in the -calculus. The v-calculus is based on two rules. The rst rule, called v, simpli es the application of an abstraction to a value. De nition .6 (Rule v) ( ( x:M) V ) v ! MfV=xg if V is a value ( v) 2Rule v states that the application of an abstraction ( x:M) to a value V can be replaced by the substitution of the parameter x in the body M by the value V . Notice that the argument of an abstraction must be a value in order to use rule v. This way of passing arguments is called call-by-value and explains why the calculus is called the call-by-value lambda-calculus, and why a subscript v appears in the notation v-calculus2. We distinguish two kinds of constants: basic constants (like numbers 0, 1, 2, . . . ) and functional constants (like +; ; : : :). The behaviour of functional constants is speci ed by a function that takes two arguments (a functional constant and a constant) and returns a constant3. 2As opposed to rule v of the call-by-value lambda-calculus, rule of the lambda-calculus does not require the argument of the abstraction to be a value. ( ( x:M ) N ) ! MfN=xg ( ) 3This de nition is voluntarily restrictive. A variant could be that the function returns a value without free variables. 4 Let +1 denote the function that adds one to its argument, and let 10 denote the function that multiplies its argument by 10. The behaviour of these functional constants is described by as follows. (+1; 0) = 1; (+1; 1) = 2; : : : ; (+1; 5) = 6; : : : ( 10; 0) = 0; ( 10; 1) = 10; : : : ; ( 10; 5) = 50; : : : The behaviour of binary arithmetic functions is also speci ed by ; the function returns a functional constant (whose behaviour is also speci ed by ). (+; 1) = +1; : : : ; (+; 10) = +10; : : : ( ; 1) = 1; : : : ; ( ; 10) = 10; : : : It should be observed that is not de ned for every pair of constants. For instance, is not de ned for (+1; ) because it is absurd to add one to the multiplication function. Now we are ready to introduce the second rule of the v-calculus. This rule simpli es the application of a constant to another constant if the function is de ned for this pair of constants. This rule is called , in the name of the function . De nition .7 (Rule ) (a b) ! (a; b) ( if a and b are constants, and if (a; b) is de ned. ( ) 2 For example, the term ( 10 5), which is equivalent to (( 10) 5), can be reduced to ( 10 5), and then to 50 by two successive applications of rule . So far, we have de ned two rules v and , which are binary relations over the set of terms . Such binary relations are usually called notions of reduction. The left-hand side of a rule is called a redex , while its right-hand side is called a contractum. Let Exp be an expression of that has a subexpression M . Let us also suppose that M is a redex with contractum N (according to rule v or ). We de ne a relation on terms of called the compatible closure, which associates Exp with an expression Exp0 where the subexpression M is replaced by the contractum N . The compatible closure is also called the one-step reduction. De nition .8 (Compatible closure !) The compatible closure of the notions of reduction v and is written !. It is de ned as follows, where ) denotes the logical implication. M v ! N ) M ! N (.6) M ! N ) M ! N (.7) M ! N ) PM ! PN (.8) M ! N ) MP ! NP (.9) M ! N ) x:M ! x:N (.10) 2 5 We de ne a reduction as a sequence of one-step reductions. In oder to reduce a term, we search for a subexpression that is a redex, we replace it by its contractum, and we repeat this process; a reduction terminates when there is no redex left, or it diverges if a redex can always be found. When a reduction terminates, the resulting term is in normal form, i.e. it does not contain any redex. De nition .9 (Reduction ! ) A reduction is the re exive, transitive closure of a onestep reduction, and is written ! . A reduction is de ned as follows, where ^ denotes the logical conjunction. M ! M M ! N ) M ! N M ! N ^ N ! P ) M ! P We write v ` M ! N if M can be proved to reduce to N by the above rules. 2 In Example .11, we illustrate the reduction of a term to a value. Beforehand, we state a convention that will be followed in this text. Convention .10 In order to facilitate the reading of the examples, each one-step reduction is subscripted by the name of the rule that is applied, and the redex chosen is placed in a box. 2 Example .11 Let (( k:( 10 (k 5))) ( u:u)) be the term to reduce. (( k:( 10 (k 5))) ( u:u)) (.11) ! (( k:( 10 (k 5))) ( u:u)) (.12) v ! ( 10 (( u:u) 5) ) (.13) v ! ( 10 5) (.14) ! 50 (.15) We say that the term (( k:( 10 (k 5))) ( u:u)) reduces to the value 50; we also write v ` (( k:( 10 (k 5))) ( u:u))! 50: 2 Sometimes, there exist several redices in the same term. Figure 1 illustrates all the possible reductions that can be performed on the term of Example .11. We observe that all reductions terminate with the same value. It is natural to wonder whether the nal result (if there exists one) can be in uenced by the order in which reductions are performed. In -calculi, the property of con uence is called the Church-Rosser property. It can be summarised by the proverb \all roads lead to Rome", and it is formally stated in Theorem .12. 6 Theorem .12 (Church-Rosser Property by Plotkin [29]) Let M1 be a term that can reduce to M2 and M3 v `M1 ! M2 and v `M1 ! M3; then, there exists a term M4, such that both M2 and M3 reduce to M4 v `M2 ! M4 and v `M3 ! M4: 2It results from Theorem .12 that the order of reduction is unimportant and that two reductions can always be permuted. 50 ( 10 5) (( k:(( 10)(k 5)))( u:u)) (( k:( 10 (k 5))) ( u:u)) (( 10) (( u:u) 5)) v v ( 10 (( u:u) 5)) ( 10 5) v v Figure 1: Several possible reductions. In Example .11, the reduction began by rule , but the Church-Rosser Theorem states that chosing rule v also leads to the same result. The choice of rule was arbitrary. Is there a systematic way to choose redices that lead to a nal solution (if there is one)? Such a systematic selection of redices is called a strategy. There is a strategy called the standard reduction, also called the left-to-right reduction, or in brief left reduction, which reduces the leftmost redex that does not appear inside an abstraction. Example .13 In Example .11, the term was not reduced by the standard strategy: the rst redex was the leftmost redex, but it appeared inside an abstraction. It can be reduced by the standard reduction as follows. (( k:( 10 (k 5))) ( u:u)) (.16) v ! ( 10 (( u:u) 5)) (.17) ! ( 10 (( u:u) 5) ) (.18) v ! ( 10 5) (.19) ! 50 (.20) 2 7 As opposed to the compatible closure (De nition .8 page 5), a redex in a standard reduction must be the leftmost, but cannot appear inside an abstraction. The de nition of the standard reduction is simply obtained from De nition .8, by removing rule (.10), and by requiring the operator to be a value in (.8). De nition .14 (Standard reduction) M ! v N ) M 7!v N (.21) M ! N ) M 7!v N (.22) M 7!v N ) MP 7!v NP (.23) M 7!v N ) V M 7!v V N if V is a value (.24) 2The left-to-right evaluation order results from rule (.24) in which the operator V is required to be a value, i.e. the operand can only be reduced after the operator is reduced to a value. We write 7! v to denote the re exive, transitive closure of 7!v; as previously, we write v `M 7! v N if M can be proved to reduce to N by the above rules. We can de ne an evaluation function, which applies the standard reduction until a value is reached (if there exists one). De nition .15 (evalv) LetM and V be a term and a value of . The evaluation function evalv is de ned as follows. evalv(M) = V if v ` M 7! v V 2The partial function evalv is de ned if M reduces to a value. This evaluation strategy is used in some programming languages. In Standard ML, [25, p. 51], [24, p. 15] the evaluation order is left to right. In Dylan [5, p. 29], Common Lisp [34, p. 75], Islisp [20, p. 19], and EuLisp [28, p. 62] the operands of an application are evaluated in a left-to-right order, but it is unspeci ed when the operator is evaluated. In contrast, in Scheme [19, p. 14], [31, p. 8], CAML [23, p. 33, p. 172], and C [21, pp. 48{50] the evaluation order of subexpressions of an application is not speci ed. From Example .13, we conclude that v ` (( k:( 10 (k 5))) ( u:u)) ! 50; and that evalv(( k:( 10 (k 5))) ( u:u)) = 50. The value of a term can be obtained by the standard strategy every time there exists a reduction of the term to a value. This property, proved by Plotkin, is formally stated in Theorem .16. Theorem .16 Let M be a term of . There exists a value V such that v `M ! V , if and only if the evaluation function is de ned for M , i.e. there exists a value V 0, such that evalv(M) = V 0. 2 Remark In Theorem .16, the value V 0 is not required to be syntactically equal to V because a reduction ! is able to reduce terms inside an abstraction, while the standard reduction does not permit it. 2 8 In a language with a left-to-right evaluation order, reductions are sequenced by two means: 1. the call-by-value strategy, which forces the evaluation of the argument of a function before the evaluation of its body, 2. the left-to-right evaluation strategy of components of an application. 2 The Notion of Context It is convenient to distinguish a redex from the term in which it appears. Let us again consider line (.11) copied below (( k:( 10 (k 5))) ( u:u)); where the chosen redex is surrounded by a box. The part of the term that appears outside the box is called the context . This context is written as follows, by replacing the box by a hole, written [ ], (( k:([ ] (k 5))) ( u:u)): Intuitively, a context is an expression that has a subexpression that is a hole. A context is formally de ned in De nition .17. De nition .17 (Context) A context C[ ] is de ned by the following grammar C[ ] ::= [ ] j (C[ ] M) j (M C[ ]) j ( x:C[ ]) with M a term of : The empty context is a context that is a hole. 2 Example .18 Let us illustrate the grammar of contexts by two examples. Let C[ ] be the context of the redex in (.11): C[ ] (( k:([ ] (k 5))) ( u:u)): We can see C[ ] as the application of a context C1[ ] to ( u:u) : C[ ] (C1[ ] ( u:u)): The context C1[ ] is an abstraction of a context C2[ ] : C1[ ] ( k:C2[ ]): The context C2[ ] is the application of a context C3[ ] to a term (k 5) : C2[ ] (C3[ ] (k 5)): And C3[ ] is the empty context [ ]. 9 Let C[ ] be the context ( 10 [ ]) of the redex in (.13). It can be written as the application of 10 to the context C1[ ] : C[ ] ( 10 C1[ ]) with C1[ ] the empty context. 2 Let C[ ] and M be a context and a term of . The notation C[M ] denotes the term obtained by placing M in the hole of C[ ]. For example, we can place the term ( 10) in the hole of the following context C[ ] (( k:([ ] (k 5))) ( u:u)); and we obtain the term C[( 10)], written (( k:(( 10) (k 5))) ( u:u)): Using the context notation, the compatible closure can be rede ned. De nition .19 (Compatible closure !) Let P and Q be two terms of ; P reduces to Q in a single step, P ! Q, if there is a context C[ ] such that P C[M ], Q C[N ], and M v ! N or M ! N . 2 The notion of context is related to the notion of subexpression. An expression M is a subexpression of a given term N if there exists a context C[ ] such that we obtain N when we place M in the context C[ ]: N C[M ]. Let us de ne the standard reduction using the context notation; we recall that the standard reduction requires to reduce the leftmost redex that does not appear inside an abstraction. Since the notion of context is used in De nition .19 to select any subexpression of a term, it is unsuitable to de ne the standard reduction. Therefore, we introduce the notion of evaluation context [12], which is able to extract from a term the leftmost redex that does not appear inside an abstraction. De nition .20 (Evaluation Context) An evaluation context E[ ] is de ned by the following grammar: E[ ] ::= [ ] j (E[ ] M) j (V E[ ]) with M a term and V a value of . 2Evaluation contexts are special contexts; let us compare their grammars. C[ ] ::= [ ] j (C[ ] M) j (M C[ ]) j x:C[ ] with M a term of E[ ] ::= [ ] j (E[ ] M) j (V E[ ]) with M a term and V a value of . Two di erences should be observed. 1. The hole of an evaluation context cannot appear inside an abstraction. This property explains why, in a programming language, the body of a function is not evaluated before the application of this function. 10 2. If an evaluation context is of the form of an application, the operator must be a value. This is why the leftmost redex that is not in an abstraction is reduced. In (.17), the evaluation context is ([ ] (( u:u)5)), while, in (.18), it is ( 10 [ ]). Let E[ ] be an evaluation context and M be a term of . The notation E[M ] denotes the term obtained by placing M in the hole of E[ ]. For example, when we place the term (( u:u) 5) in the following evaluation context E[ ] ( 10 [ ]); we obtain the term ( 10 (( u:u) 5)), which is written E[(( u:u) 5)]. As we did for the compatible closure with the notion of context, we can de ne the standard reduction with the notion of evaluation context [12]. De nition .21 (Standard Reduction) Let P and Q be two terms of . There is a standard reduction from P to Q, P 7!v Q, if there exists an evaluation context E[ ], such that P E[M ], Q E[N ], and M v ! N or M ! N . Put di erently, a standard reduction is given by one of the following two transitions: E[( x:M) V ] 7!v E[MfV=xg] if V is a value ( v) E[(a b)] 7!v E[ (a; b)] ( ) 2 Example .22 We consider again the term of Example .13, and we show the evaluation context for each transition. evaluation context term (( k:( 10 (k 5))) ( u:u)) [ ] (.25) v ! ([ ] (( u:u) 5)) ( 10 (( u:u) 5)) (.26) ! ( 10 [ ]) ( 10 (( u:u) 5) ) (.27) v ! [ ] ( 10 5) (.28) ! 50 (.29) 2 A na ve way of programming the evaluation function is presented in Program .24. This program illustrates how the leftmost redex that does not appear inside an abstraction can be searched in a term. But rst we explain the record facility that we use in our programs. 11 Convention .23 We use the same record facility as Friedman, Wand, and Haynes [16]. A new record type is de ned by an expression (define-record name (field1 : : : fieldn)). This expression de nes a constructor, a predicate, and accessors: 1. a procedure make-name to create new records of type name, 2. a predicate name?, which returns true when its argument is a record of type name, 3. accessor functions name->fieldi taking a record of type name and returning the value of its fieldi. For instance, at the top of Figure 2, we de ne the abstract syntax of terms of the vcalculus using this record facility. 2 Program .24 The evaluation function can be na vely programmed by the following stages: search for the leftmost redex, reduce the leftmost redex, re-build the expression where the redex is replaced by its contractum, repeat this process until no redex is left. Such a strategy is implemented in the program of Figure 2. Three cases are considered in the function reduce-leftmost-redex: 1. if the expression is a value, then the evaluation terminates, returning the boolean #f (meaning that no reduction was performed); 2. if the expression is a redex, then the result is the contractum of rules v or ; 3. if the expression is an application (but not a redex), then the leftmost redex is searched; if it is found, a new expression is returned in which the leftmost redex is replaced by its contractum; if no redex is found, #f is returned. The function eval-leftmost calls the function reduce-leftmost-redex until a normal form is obtained; eval-leftmost computes the re exive, transitive closure of the onestep reduction. The function reduce takes a function (abstraction or functional constant) and a value as arguments and returns the contractum. The code is not included in Figure 2 but is supposed to satisfy rules v and . 2 The implementation of the evaluation function in Program .24 is na ve because each reduction builds a new term and searches for the next leftmost redex. Let us consider the two transitions of the standard reduction (De nition .21); they can be summarised by the following transition, which does not distinguish between v and . P E[redex] 7!v Q E[contractum] If Q is obtained after one leftmost reduction of P , the contractum still appears in the same evaluation context E[ ]. It means that the search for the leftmost redex follows the same path in Q as it did in P , i.e. it follows the common evaluation context E[ ]. Therefore, a natural optimisation would be to remember the path that was followed to reach the leftmost redex. We shall do so in Program .28, which uses a stack to evaluate an expression; beforehand, let us de ne the notion of continuation, of which a stack is a possible representation. 12 (define-record constant (val)) (define-record variable (var)) (define-record applic (operator operand)) (define-record lambda (parameter body)) (define value? (lambda (exp) (or (lambda? exp) (variable? exp) (constant? exp)))) (define beta-redex? (lambda (exp) (and (applic? exp) (lambda? (applic->operator exp) (value? (applic->operand exp)))))) (define delta-redex? (lambda (exp) (and (applic? exp) (constant? (applic->operator exp)) (constant? (applic->operand exp)) (delta-defined? (applic->operator exp) (applic->operand exp))))) (define redex? (lambda (exp) (or (beta-redex? exp) (delta-redex? exp)))) (define reduce (lambda (function arg) (if (constant? function) (delta-reduce function arg) (beta-reduce function arg)))) (define reduce-leftmost-redex (lambda (exp) (cond ((value? exp) #f) ((redex? exp) (reduce (applic->operator exp) (applic->operand exp))) ((applic? exp) (let ((rator (applic->operator exp)) (rand (applic->operand exp))) (let ((reduced-rator (reduce-leftmost-redex rator))) (if reduced-rator (make-applic reduced-rator rand) (let ((reduced-rand (reduce-leftmost-redex rand))) (if reduced-rand (make-applic rator reduced-rand) #f)))))) (else (error "reduce-leftmost-redex: unkown exp" exp))))) (define eval-leftmost (lambda (exp) (let ((result (reduce-leftmost-redex exp))) (if result (eval-leftmost result) exp)))) Figure 2: Na ve approach to evaluation 13 3 The Notion of Continuation The evaluation function de nes the order in which redices must be reduced. Given a term, we can specify the redices that will be reduced after the rst redex (in the standard strategy). In the 1970s, the word continuation was coined to name the sequencing of operations. Terminology .25 (Continuation) The continuation of an expression is the sequence of operations that remain to be performed after the value of the expression is obtained. 2 An evaluation context represents the notion of continuation. In the expression E[M ], the evaluation context E[ ] is a representation of the continuation of M ; that is to say, it represents the operations that remain to be performed when we obtain the value of M . Example .26 In Example .22, line (.26), the evaluation context is ([ ] (( u:u) 5)). It is a representation of the continuation of the redex ( 10). The meaning of this continuation is that, when we obtain a value, we still have to apply it to the result of the application of ( u:u) to 5. In (.28), the redex ( 10 5) appears in the empty evaluation context [ ]. This context represents the continuation of the redex; it means that, after evaluation of ( 10 5), there is nothing left to do. 2 In the framework of functional programming, it is usual to say that \the value of an expression M is V ", or \the expression M returns the value V "; such a terminology means that there is a reduction of M to V according to the evaluation function. If we consider that there is a notion of continuation in an evaluation process, we shall say that the value V of expression M \is passed" to the continuation of M . It is also usual to say that, whenever an expression is evaluated, there is a continuation that \wants" or \waits for" the value of this expression. We shall use the terminology \current continuation" to designate the continuation of an expression at a given time. Let c be the continuation that is current when the expression (f v) is evaluated; c is said to be the continuation of the call of f . It is usual to implement an evaluator of a programming language with a stack . This idea was already suggested in the discussion that followed Program .24: the stack is the technique that we proposed to remember the path that was followed to reach the leftmost redex. We can view the stack as another way to code an evaluation context, which is itself a representation of the continuation. Therefore, an evaluation stack is another representation of a continuation in an evaluator. Below, we propose an evaluation method that uses a stack. During evaluation of an application, we have to deal with two cases: we can evaluate either the operator or the operand of an application. 14 Example .27 The leftmost redex of term (.30) is (( x:x)( y:y)). (( x:x)( y:y))( z:z) (.30) If we evaluate (.30), we have rst to evaluate (( x:x)( y:y)). We have to remember that we are examining the operator of (.30). Therefore, we have to push on the stack an information indicating that we still have to evaluate the operand ( z:z). Similarly, the leftmost redex of term (.31) is (( y:y)( z:z)). ( x:x)(( y:y)( z:z)) (.31) The evaluation of (.31) requires to evaluate (( y:y)( z:z)). So, we have to remember that we are evaluating the operand of (.31). Therefore, we have to push on the stack an information indicating that the value of the operator of (.31) is ( x:x). 2 Example .27 shows that two kinds of information can be used while evaluating terms of : either the operator of an application has a value V , or the operand of an application is an expressionM . Hence, we introduce two new data structures of one slot: c/fun1 and c/arg1. A c/fun1 data structure contains the value of an operator; it means that the operator of an application is already evaluated, and that the operand of the application is being evaluated. Symmetrically, a c/arg1 data structure contains an operand; it means that the operator is being evaluated, and that the operand is yet to be evaluated. Program .28 In Figure 3, the function eval-stack takes an expression exp and a stack as arguments. The function eval-stack considers two cases for the expression exp being evaluated. If the expression is an application (the second clause), a c/arg1 data structure containing the operand of this application is pushed on the stack, and the operator is evaluated. Eventually, when a value is reached (the rst clause), the function apply-stack is called. The function apply-stack dispatches on the content of the stack. When the stack is empty, the received value val is the nal result. If the top of the stack is a c/arg1 data structure, then val is the value of the operator, and the operand is yet to be evaluated; therefore, the c/arg1 structure is popped from the stack, a data structure c/fun1 with the value val is pushed on the stack, and the operand held in the c/arg1 structure is evaluated. Otherwise, if the top of the stack is a c/fun1 data structure, then both the operator and the operand of an application are already evaluated; the redex can be reduced with the content of the c/fun1 structure and the value val. 2 An evaluator using a stack as in Program .28 avoids the ine ciency of the na ve evaluator of Program .24: the search for the next redex is not repeated at each reduction step. In this dissertation, we illustrate the evaluation of several programs using a stack. In order to facilitate the presentation, we introduce a graphical representation of a stack described in Convention .29. Convention .29 We explicitly indicate the base and the top of a stack with the pointers base and top. In our representation, pushing an item on the stack moves upward the top pointer, and popping an item moves it downward. A stack is composed of blocks that correspond to data structures c/fun1 and c/arg1 in Program .28. 15 (define-record constant (val)) (define-record variable (var)) (define-record applic (operator operand)) (define-record lambda (parameter body)) (define push cons) (define pop cdr) (define top car) (define the-empty-stack '()) (define empty-stack? null?) (define-record c/arg1 (exp)) (define-record c/fun1 (value)) (define value? (lambda (exp) (or (lambda? exp) (variable? exp) (constant? exp)))) (define reduce (lambda (function arg) (if (constant? function) (delta-reduce function arg) (beta-reduce function arg)))) (define eval-stack (lambda (exp stack) (cond ((value? exp) (apply-stack stack exp)) ((applic? exp) (eval-stack (applic->operator exp) (push (make-c/arg1 (applic->operand exp)) stack))) (else (error "eval-stack: Unkown expression" exp))))) (define apply-stack (lambda (stack val) (cond ((empty-stack? stack) val) ((c/arg1? (top stack)) (eval-stack (c/arg1->exp (top stack)) (push (make-c/fun1 val) (pop stack)))) ((c/fun1? (top stack)) (eval-stack (reduce (c/fun1->value (top stack)) val) (pop stack))) (else (error "Apply-stack: Unknown top of stack " stack))))) --> to evaluate: (eval-stack the-empty-stack) Figure 3: Evaluation with a stack 16 The block (+1 [ ]) represents a c/fun1 data structure that contains the function +1; it means that the expression being evaluated will yield a value to which the function +1 will be applied. The block ([ ] M) represents a c/arg1 data structure containing an expression M ; it means that the expression being evaluated will yield a value that will be applied to the value of M (remaining to evaluate). 2 In Example .30, using Convention .29, we give all the snapshots of the stack during evaluation of an expression. As the reader will observe, there are many intermediate stacks. In the sequel, we shall give only some of the stack representations. Example .30 Let us evaluate the expression (( k:( 10 (k 5))) ( u:u)) with a stack. All the stack representations are illustrated in Figure 4. After the rst vreduction, we obtain the con guration 4. After the -reduction, we obtain representation 8. After the v-reduction, we obtain representation 12, and the nal result is obtained in representation 13. 2 So far, we have de ned the concept of continuation, and we have presented two di erent representations of a continuation; an evaluation context is the representation of the continuation of a redex in the v-calculus, and a stack is the representation of the continuation of an expression in an evaluator. In Sections 4 and 5, we present two new representations of continuations. First, a new type of data structure called continuation code is used to implement an abstract machine, called the CEK-machine. Second, we derive an evaluator where continuations are represented as regular Scheme functions. This evaluator leads to a programming style called \continuation-passing style". 4 The CEK-Machine 4.1 Continuation Codes Data structures that are pushed on the stack, such as c/fun1 and c/arg1 records, are usually called stack frames [13]. The stack in Program .28 is just a list of stack frames. Instead of using the single-slot data structures chained by a list, we could use a new type of data structures with two slots: the rst slot contains, as previously, either the value of an operator or an operand, while the second slot is used for chaining. These new data structures are called continuation codes [12]. An empty stack was represented by an empty list in Figure 3. Thus, we introduce a new continuation code c/init, called the initial continuation. Such a continuation code has no slot; it is meant to represent the end of a continuation. 17 1 exp: (( k:( 10 (k 5))) ( u:u)) stack: 6 base = top 2 exp: ( k:( 10 (k 5))) stack: 6 base ([ ] ( u:u)) top 3 exp: ( u:u) stack: 6 base (( k:( 10 (k 5))) [ ]) top 4 exp: ( 10 (( u:u) 5)) stack: 6 base = top 5 exp: ( 10) stack: 6 base ([ ] (( u:u) 5)) top 6 exp: stack: 6 base ([ ] (( u:u) 5)) ([ ] 10) top 7 exp: 10 stack: 6 base ([ ] (( u:u) 5)) ( [ ]) top 8 exp: 10 stack: 6 base ([ ] (( u:u) 5)) top 9 exp: (( u:u) 5) stack: 6 base ( 10 [ ]) top 10 exp: ( u:u) stack: 6 base ( 10 [ ]) ([ ] 5) top 11 exp: 5 stack: 6 base ( 10 [ ]) (( u:u) [ ]) top 12 exp: 5 stack: 6 base ( 10 [ ]) top 13 exp: 50 stack: 6 base = top Figure 4: Stack representations 18 Program .31 An evaluator using continuation codes is displayed in Figure 5. Three continuation codes c/arg, c/fun, and c/init are de ned at the top of Figure 5. The evaluator of Figure 5 can be easily derived from the evaluator of Figure 3: we just have to replace the stack by these continuation codes as follows. 1. Referencing the slot of the structure at the top of the stack in Figure 3 becomes referencing the slot of the current continuation code in Figure 5. (c/arg1->exp (top stack)) ! (c/arg->exp cont) (c/fun1->value (top stack)) ! (c/fun->value cont) 2. Pushing a frame on the stack in Figure 3 becomes creating a new continuation code with the information contained in the frame and the current continuation code in Figure 5. (push (make-c/arg1 M) stack) ! (make-c/arg M cont) (push (make-c/fun1 V) stack) ! (make-c/fun V cont) 3. Popping the stack in Figure 3 becomes referencing the slot rest of the current continuation code in Figure 5. (pop stack) ! (c/arg->rest cont) (c/fun->rest cont) 2 A continuation code is yet another way of representing a stack. Therefore, continuation codes and stacks are both representations of the operations that remain to be performed after evaluating of the current expression; that is, continuation codes and stacks are both representations of the notion of continuation. The evaluator of Figure 5 can be formulated in a more abstract notation, called the CEK-machine. The CEK-machine is an abstract machine that uses continuation codes to explicitly represent its continuation. The CEK-machine di ers from Figure 5 by the use of an environment; it is described in Section 4.2. 4.2 The CEK-Machine Felleisen and Friedman [12] introduced the CEK-machine, an abstract machine with a control string C, an environment E, and a continuation K, as a variant of Landin's SECD-machine [22, 17, 3]. The CEK-machine consists of three elements. 1. A control string is either an expression of , or the distinguished symbol z. 2. An environment maps variables to semantic values. Semantic values are constants or closures h x:M; i, where is also an environment containing in its domain the free variables of x:M . The extension of the environment with a binding between x and the value V is written [x := V ]. 3. A continuation code represents the rest of the computation; it encodes what remains to be performed after evaluating the current control string. There are two kinds of continuation codes. 19 (define-record constant (val)) (define-record variable (var)) (define-record applic (operator operand)) (define-record lambda (parameter body)) (define-record c/arg (exp rest)) ;;; continuation codes (define-record c/fun (value rest)) (define c/init '()) (define c/init? null?) (define value? (lambda (exp) (or (lambda? exp) (variable? exp) (constant? exp)))) (define reduce (lambda (function arg) (if (constant? function) (delta-reduce function arg) (beta-reduce function arg)))) (define eval-cont (lambda (exp cont) (cond ((value? exp) (apply-cont cont exp)) ;[1] ((applic? exp) (eval-cont (applic->operator exp) ;[2] (make-c/arg (applic->operand exp) cont))) (else (error "eval-cont: Unknown exp" exp))))) (define apply-cont (lambda (cont val) (cond ((c/init? cont) val) ;[3] ((c/arg? cont) (eval-cont (c/arg->exp cont) ;[4] (make-c/fun val (c/arg->rest cont)))) ((c/fun? cont) (eval-cont (reduce (c/fun->value cont) val) ;[5] (c/fun->rest cont))) (else (error "Apply-cont: Unknown continuation" cont))))) --> to evaluate: (eval-cont c/init) Figure 5: Continuation-based evaluation 20 A p-continuation is of the form (stop); ( arg N ); ( fun V ), where is a p-continuation, and V a semantic value. These continuation codes respectively correspond to the c/init, c/arg, and c/fun continuation data structures in the evaluator of Figure 5. The code (stop) is the initial continuation, while ( arg N ) and ( fun V ) respectively mean that an operand N is yet to be evaluated, and that an operator was evaluated to the value V . A ret-continuation is of the form( ret V ), with a p-continuation and V a semantic value. Such a continuation is used when an expression returns a value V to the current continuation . It corresponds to a call of apply-cont by eval-cont in Figure 5. A state of the machine is either a triple hM; ; i with M an expression of and a p-continuation, or a triple hz; ;; i with a ret-continuation. In order to evaluate an expression M with the CEK-machine, we begin the computation with the initial state hM; ;; (stop)i, and we end the computation when a terminal state is reached; such a terminal state is of the form hz; ;; ((stop) ret V )i. Transitions between states are performed according to De nition .32. De nition .32 (CEK-machine) hx; ; i CEK 7! hz; ;; ( ret (x))i (cek1) h x:M; ; i CEK 7! hz; ;; ( ret h x:M; i)i (cek2) hc; ; i CEK 7! hz; ;; ( ret c)i if c is a constant (cek3) hMN; ; i CEK 7! hM; ; ( arg N )i (cek4) hz; ;; (( arg N ) ret V )i CEK 7! hN; ; ( fun V )i (cek5) hz; ;; (( fun h x:M; i) ret V )i CEK 7! hM; [x := V ]; i (cek6) hz; ;; (( fun a) ret b)i CEK 7! hz; ;; ( ret (a; b))i if (a; b) is de ned.(cek7) 2 Let us compare the CEK-machine transitions of De nition .32 with the evaluator of Figure 5. Rules cek1, cek2, cek3 correspond to line [1] in Figure 5. Indeed, a variable x, an abstraction x:M , and a constant c are values; their corresponding semantic values, (x), h x:M; i, and c, are passed to the current continuation . After transitions cek1, cek2, and cek3 the CEK-machine is in a state hz; ;; ( ret V )i, which corresponds to a call to apply-cont in the evaluator of Figure 5. Rule cek4 corresponds to line [2], which launches the evaluation of the operator of an application after creation of a continuation arg or c/arg respectively. The function apply-cont dispatches on the type of the continuation. Line [4] corresponds to rule cek5, and line [5] corresponds to cek6 and cek7. A nal state of the 21 CEK-machine is de ned by a triple of the form hz; ;; ((stop) ret V )i; it corresponds to line [3] in the apply-cont function detecting a c/init continuation. The major di erence between the CEK-machine and the program of Figure 5 is the use of an environment which binds free variables to semantics values. We can abstract the evaluation process by the evalcek function. De nition .33 (evalcek) Let M and V be a term and a value of . The evaluation function evalcek is de ned as follows.evalcek(M) = V if there are some transitions from the initial con guration to a nal con guration of the CEK-machine: hM; ;; (stop)i CEK 7! + hz; ;; ((stop) ret V )i: 2Example .34 Using the CEK-machine, let us evaluate the expression (( k:( 10 (k 5))) ( u:u)) of Example .30. The evaluation is displayed in Figure 6 for the sake of completeness. We can observe that the number of transitions is higher than in Example .30 because transitions are introduced in the CEK-machine to convert values to semantics values. Figure 6 can be compared with Figure 4. For instance, line (.47) copied below corresponds to the con guration 11 of the stack in Figure 4. cek5 ! h5; 1; (((stop) fun 10) fun h( u:u); i)i The control string (the current expression) is 5, and the continuation code indicates that two operators have already been evaluated. 2 So far, we have presented the v-calculus, and we have seen that an evaluation context represents the continuation of a term. For e ciency reasons, we have used a stack to implement the evaluation function of the v-calculus, and we have derived an abstract machine that used data structures called continuation codes to represent continuations. In Section 5, we derive a new evaluator in which continuations are represented by regular Scheme functions. This will lead to the de nition of a programming style called \continuation-passing style", or cps for short. 5 Continuation-Passing Style In this section, from the evaluator using continuation codes (Figure 5), we derive an evaluator in which continuations are represented by Scheme functions. Three stages compose the derivation; they are described in Programs .35, .36, and .37. In the rst stage, in Program .35, the behaviour associated with a continuation is encoded by a Scheme function. 22 h(( k:( 10 (k 5))) ( u:u)); ; (stop)i (.32) cek4 ! h( k:( 10 (k 5))); ; ((stop) arg ( u:u) )i (.33) cek2 ! hz; ;; (((stop) arg ( u:u) ) ret h( k:( 10 (k 5))); i)i (.34) cek5 ! h( u:u); ; ((stop) fun h( k:( 10 (k 5))); i)i (.35) cek2 ! hz; ;; (((stop) fun h( k:( 10 (k 5))); i) ret h( u:u); i)i (.36) cek6 ! h( 10 (k 5)); 1 [k := h( u:u); i]; (stop)i (.37) cek4 ! h( 10); 1; ((stop) arg (k 5) 1)i (.38) cek4 ! h ; 1; (((stop) arg (k 5) 1) arg 10 1)i (.39) cek3 ! hz; ;; ((((stop) arg (k 5) 1) arg 10 1) ret )i (.40) cek5 ! h10; 1; (((stop) arg (k 5) 1) fun )i (.41) cek3 ! hz; ;; ((((stop) arg (k 5) 1) fun ) ret 10)i (.42) cek7 ! hz; ;; (((stop) arg (k 5) 1) ret 10)i (.43) cek5 ! h(k 5); 1; ((stop) fun 10)i (.44) cek4 ! hk; 1; (((stop) fun 10) arg 5 1)i (.45) cek1 ! hz; ;; ((((stop) fun 10) arg 5 1) ret h( u:u); i)i (.46) cek5 ! h5; 1; (((stop) fun 10) fun h( u:u); i)i (.47) cek3 ! hz; ;; ((((stop) fun 10) fun h( u:u); i) ret 5)i (.48) cek6 ! hu; [u := 5]; ((stop) fun 10)i (.49) cek1 ! hz; ;; (((stop) fun 10) ret 5)i (.50) cek7 ! hz; ;; ((stop) ret 50)i (.51) Figure 6: An evaluation with the CEK-machine 23 Program .35 Let us again examine Figure 5. In eval-cont, when the expression to be evaluated is a value, the function apply-cont is applied to the current continuation and to the expression. The function apply-cont dispatches on the type of the continuation: for each type of continuation, there is an associated behaviour . For example, the behaviour associated with a c/fun continuation is (eval-cont (reduce (c/fun->value cont) val) (c/fun->rest cont)). We can de ne a function that returns the behaviour of a c/fun continuation as follows. (define behaviour-of-c/fun (lambda (cont) (lambda (val) (eval-cont (reduce (c/fun->value cont) val) (c/fun->rest cont))))) The behaviour is a function of the form (lambda (val) ...) that returns a nal result when provided a value val. Now, we can transform the evaluator of Figure 5 into the evaluator displayed in Figure 7 where, for each type of continuation, we de ne a function that returns its behaviour. The function apply-cont is in fact a generic function that applies the behaviour of a continuation to the value val. 2 In the second stage, each continuation code, which is associated with a unique behaviour, is replaced by its behaviour. Program .36 describes this transformation. Program .36 Since a continuation is uniquely associated with a behaviour by the function apply-cont, we can directly represent a continuation by its behaviour, i.e. a function of the form (lambda (val)...). Thus, we replace the continuation data structures by their behaviours. We keep the identi ers make-c/arg, make-c/fun, make-c/init, but instead, we bind them to functions de ning the behaviour of continuations. For instance, the constructor make-c/fun is de ned as follows. (define make-c/fun (lambda (fun cont) (lambda (val) (eval-cont (reduce fun val) cont)))) The constructor make-c/fun requires a function and a continuation as in Figure 7, but it returns a function (lambda (val) ...). This function is the same as the body of the function behaviour-of-c/fun where (c/fun->value cont) and (c/fun->rest cont) were replaced by the parameters fun and val respectively. We can proceed similarly for the other continuations. Figure 8 shows the new de nitions of the continuation constructors. A similar transformation must also be applied to apply-cont. We said in Program .35 that apply-cont was a generic function that applied the behaviour of a continuation to a value. Now that a continuation is represented directly by its behaviour, apply-cont consists in applying a continuation to a value [16, p. 303]. 2 Remark We can observe that the function apply-cont in Figure 8 no longer contains a conditional expression. Does it mean that the program transformation presented here can be used to remove conditional expressions? Not really. Even though the conditional expression has disappeared from the program, it is still implicitly present. Indeed, in Figure 5, each operator of an application is a global identi er that is bound to a single function. On the contrary, in the 24 (define-record constant (val)) (define-record variable (var)) (define-record applic (operator operand)) (define-record lambda (parameter body)) (define-record c/arg (exp rest)) (define-record c/fun (value rest)) (define c/init '()) (define c/init? null?) (define value? (lambda (exp) (or (lambda? exp) (variable? exp) (constant? exp)))) (define reduce (lambda (function arg) (if (constant? function) (delta-reduce function arg) (beta-reduce function arg)))) (define eval-cont (lambda (exp cont) (cond ((value? exp) (apply-cont cont exp)) ((applic? exp) (eval-cont (applic->operator exp) (make-c/arg (applic->operand exp) cont))) (else (error "eval-cont: Unkown expression" exp))))) (define behaviour-of-c/init (lambda (cont) (lambda (val) val))) (define behaviour-of-c/arg (lambda (cont) (lambda (val) (eval-cont (c/arg->exp cont) (make-c/fun val (c/arg->rest cont)))))) (define behaviour-of-c/fun (lambda (cont) (lambda (val) (eval-cont (reduce (c/fun->value cont) val) (c/fun->rest cont))))) (define apply-cont (lambda (cont val) (cond ((c/init? cont) ((behaviour-of-c/init cont) val)) ((c/arg? cont) ((behaviour-of-c/arg cont) val)) ((c/fun? cont) ((behaviour-of-c/fun cont) val)) (else (error "Apply-cont: Unknown continuation" cont))))) --> to evaluate: (eval-cont c/init) Figure 7: Continuation-based evaluation (2) 25 (define eval-cont (lambda (exp cont) (cond ((value? exp) (apply-cont cont exp)) ((applic? exp) (eval-cont (applic->operator exp) (make-c/arg (applic->operand exp) cont))) (else (error "eval-cont: Unkown expression" exp))))) (define make-c/fun (lambda (fun cont) (lambda (val) (eval-cont (reduce fun val) cont)))) (define make-c/arg (lambda (exp cont) (lambda (val) (eval-cont exp (make-c/fun val cont))))) (define c/init (lambda (val) val)) (define apply-cont (lambda (cont val) (cont val))) --> to evaluate: (eval-cont c/init) Figure 8: Continuation-based evaluation (3) body of apply-cont in Figure 8, the application (cont val) has an operator cont that can be bound to three di erent functions generated by make-c/init, make-c/arg, and make-c/fun. At the implementation level, it must be decided, at run time, which function is applied in (cont val). 2 The third and last stage of the derivation simply consists in replacing the continuation constructors by their de nitions, which is done in Program .37. Program .37 Let us replace make-c/fun by its de nition in make-c/arg, and let us replace make-c/arg by its new de nition in eval-cont. We obtain the code of Figure 9 where the function eval-cont is renamed eval-cps, and the val parameters for c/arg and c/fun continuations are renamed fct and arg respectively so as to avoid name clashes. 2 Although the versions of the evaluation function of Figure 8 and Figure 9 are essentially equivalent, the latter is characteristic of a programming style called \continuationpassing style", or cps for short [32]. Such a style has the following features. The evaluation function takes an extra-argument called a continuation, which represents what remains to be evaluated after the evaluation of the rst argument. Instead of returning a value, the evaluation function passes the value to its continuation. The continuation is a function that returns a nal answer when provided a value. By the existence of a continuation, each call to the evaluation function contains all operations that remain to be performed to complete the computation. 26 (define eval-cps (lambda (exp cont) (cond ((value? exp) (cont exp)) ((applic? exp) (eval-cps (applic->operator exp) (lambda (fct) (eval-cps (applic->operand exp) (lambda (arg) (eval-cps (reduce fct arg) cont)))))) (else (error "eval-cps: Unknown expression" exp))))) (define the-init-cont (lambda (x) x)) --> to evaluate: (eval-cps the-init-cont) Figure 9: Continuation-passing style evaluator At this level, several conclusions are necessary. The evaluators of Figures 3, 5, 9 use di erent techniques for coding continuations: stack, continuation codes, or function. In all cases, these representations are equivalent to the notion of evaluation context [12]. Although we have succeeded in our goal of implementing the evalv function, we have lost the simplicity of the v-calculus. Indeed, it was composed of two simple rules ( v and ) from which a compatible closure was generated. In contrast, not only is the CEK-machine based on ve rules, but also it introduces a new set of terms: the set of continuation codes. We have now completed the presentation of the di erent possible representations of a continuation: evaluation context, stacks, continuation codes, and functions. The last representation results in a programming style called \continuation-passing style", in which every function takes an extra-argument that is the representation of its continuation by a regular function. In Section 6, we compare this programming style with the \usual programming style" in which continuations are not written explicitly. 6 Direct Style The function eval-cps of Figure 9 is a tail-recursive4 function that traverses an expression in a left-to-right manner and builds a continuation for what remains to be evaluated. Scheme, however, allows the de nition of recursive functions, and instead of explicitly building a continuation in the program, we can rely on the recursion mechanism. A recursive version of the evaluator appears in Program .38. 4The function eval-cps is syntactically tail-recursive because all recursive calls are in terminal position. Thus, we do not need a control stack to evaluate a program such as eval-cps. Nevertheless, this function remains fundamentally recursive because the number of allocated continuations grows with the depth of the recursion. 27 Program .38 A recursive function eval-direct is presented in Figure 10. In the base case, the expression to be evaluated is a value and simply is returned. The inductive case deals with the evaluation of an application. The operator and the operand are reduced to values, then the redex is reduced, and the resulting contractum is evaluated with the same strategy. (define eval-direct (lambda (exp) (cond ((value? exp) exp) ((applic? exp) (let* ((fct (eval-direct (applic->operator exp))) (arg (eval-direct (applic->operand exp)))) (eval-direct (reduce fct arg)))) (else (error "eval-direct: Unknown exp" exp))))) Figure 10: Direct style Notice that we had to insert a let* in the de nition in order to force a left-to-right evaluation order. Indeed Scheme does not specify the evaluation order in applications. 2 As opposed to eval-cps of Figure 9, eval-direct is said to be in direct style [33, 7]. There is a di erence between eval-cps and eval-direct as far as continuations are concerned: the former explicitly represents the continuation of the evaluation process, while the latter hides its continuation in the recursion mechanism. The evaluation functions of Figures 9 and 10 can be considered as de nitional interpreters of a language, i.e. interpreters de ning the semantics of a language. The interpreter of Figure 9 is said to be a continuation semantics, while the interpreter of Figure 10 is said to be a direct semantics. The direct and the cps styles have nice properties. It has been known for a while that there is a translation from the direct style to the cps style. This translation is called the cps translation; it was rst formalised by Fischer [14], Reynolds [32], and Plotkin [29]. 7 The CPS Translation Figure 9 shows a continuation-passing style interpreter for the call-by-value -calculus; it takes an expression and a continuation as arguments, and passes the value of this expression to the continuation, which is a unary function. On the contrary, Figure 10 displays an interpreter that is in direct style; it takes a single argument, which is the expression to evaluate, and returns its value. In this section, we derive a translator to cps from the evaluator of Figure 9. Such a translator takes a term of , said to be in direct style, and generates another term of , which is the continuation-passing style version of the source term. The translation to continuation-passing style is an important technique. First, it makes the notion of continuation explicit, and helps to understand the notion of continuation. Second, cps programs do not require a control stack to be evaluated because all function calls are terminal, as explained in Section 5; we shall use this property to 28 implement an evaluator for terms of . Third, the cps translation is a technique used to prove properties of programs in this dissertation. We derive the cps translator as follows. First, we add an environment to the evaluator of Figure 9 so that evaluation can return semantic values; environments and closures, which are introduced by this transformation, are represented by Scheme functions. Second, we curry the evaluation function in order to separate the static and the dynamic parts of evaluation [16]. Third, instead of evaluating an expression, we generate code using the same recursion scheme. 7.0.1 Environments and Closures Represented by Abstractions The function eval-cps in Figure 9 calls the function reduce, which performs the v or reductions. In order to avoid the ine ciency of substitution (used in the v-reduction), it is usual to introduce an environment, as we already did for the CEK-machine. An environment is a nite function that maps variables to semantic values. Semantic values can be constants or closures, which consist of an abstraction and an environment. Program .39 In Figure 11, the function eval-cps is given a new parameter env, which is the current evaluation environment. The semantic value associated with a variable is obtained by the application of the environment to this variable. The semantic value associated with a constant is the constant itself. The semantic value associated with an abstraction is a closure obtained by the constructor make-closure, which will be described later. As in Figure 9, semantic values are passed to the current continuation cont. During the evaluation of an application, the continuation of the operand is (lambda (arg) (apply-closure fct arg cont)), where fct is expected to be a closure resulting from the evaluation of the operator, arg is the semantic value associated with the operand, and cont is the continuation of the application. Even though there exist several other representations for closures (e.g. lists, records, . . . ), we decide to represent closures by Scheme functions. In Figure 12, for a given abstraction exp and a given environment env, the constructor make-closure returns a Scheme function that takes two parameters arg and c. This function evaluates the body of the abstraction exp after extension of the environment env with a binding between the parameter of the abstraction and the received argument arg. The function apply-closure requires three arguments: a closure, a value, and a continuation; apply-closure simply applies the Scheme function that represents the closure to the value and to the continuation. 2 Program .40 Let us replace the functions make-closure and apply-closure by their de nitions in eval-cps so as to obtain the code of Figure 13. 2 29 (define eval-cps (lambda (exp env cont) (cond ((variable? exp) (cont (env exp))) ((constant? exp) (cont exp)) ((lambda? exp) (cont (make-closure exp env))) ((applic? exp) (eval-cps (applic->operator exp) env (lambda (fct) (eval-cps (applic->operand exp) env (lambda (arg) (apply-closure fct arg cont)))))) (else (error "eval-cps: Unknow expression" exp))))) (define the-init-cont (lambda (x) x)) (define the-init-env (lambda (x) (error "unknown variable" env))) --> to evaluate: (eval-cps the-init-env the-init-cont) Figure 11: Continuation-passing style evaluator (with environment) (1) (define make-closure (lambda (exp env) (lambda (arg c) (eval-cps (lambda->body exp) (extend env (lambda->parameter exp) arg) c)))) (define apply-closure (lambda (fct arg cont) (fct arg cont))) (define extend (lambda (env var val) (lambda (x) (if (equal? x var) val (env x))))) Figure 12: Representation of closures by Scheme functions (define eval-cps (lambda (exp env cont) (cond ((variable? exp) (cont (env exp))) ((constant? exp) (cont exp)) ((lambda? exp) (cont (lambda (arg c) (eval-cps (lambda->body exp) (extend env (lambda->parameter exp) arg) c)))) ((applic? exp) (eval-cps (applic->operator exp) env (lambda (fct) (eval-cps (applic->operand exp) env (lambda (arg) (fct arg cont)))))) (else (error "eval-cps: Unknow expression" exp))))) Figure 13: Continuation-passing style evaluator (with environment) (2) 30 7.0.2 The Static and the Dynamic Parts of Evaluation In the second stage, we curry the evaluation function obtained in Figure 13. This transform preludes to the nal stage where the evaluation will be replaced by the generation of code. Program .41 The function eval-cps, which was of the form (lambda (exp env cont) ...), in Figure 13 becomes (lambda (exp env) (lambda (cont) ...)) in Figure 14. Similarly, a closure, which was of the form (lambda (arg c) ...) in Figure 13, becomes (lambda (arg) (lambda (c) ...)) in Figure 14. (define eval-cps (lambda (exp env) (lambda (cont) (cond ((variable? exp) (cont (env exp))) ((constant? exp) (cont exp)) ((lambda? exp) (cont (lambda (arg) (lambda (c) ((eval-cps (lambda->body exp) (extend env (lambda->parameter exp) arg)) c))))) ((applic? exp) ((eval-cps (applic->operator exp) env) (lambda (fct) ((eval-cps (applic->operand exp) env) (lambda (arg) ((fct arg) cont)))))) (else (error "eval-cps: Unknown expression" exp)))))) Figure 14: Continuation-passing style evaluator (with environment) (3) Notice that the predicates in the conditional expression of Figure 14 are independent of the parameter cont. In Figure 15, the function (lambda (cont) ...) is \distributed" over the conditional expression. 2 Equation (.52) displays the signature of the function eval-cps that appears in Figure 15, with the following conventions. Let a ! b denote the type of a function that takes an argument of type a and returns a value of type b. Let (a b)! c denote the type of a function that takes an argument of type a and an argument of type b, and returns a value of type c. eval-cps : (exp env) ! (cont ! semantic-value) (.52) cont : semantic-value ! semantic-value semantic-value : semantic-value ! (cont ! semantic-value) j constant env : variable ! semantic-value exp 2 variable 2 In retrospect, we can understand why, in Program .39, we chosed to represent closures by Scheme functions: in the function eval-cps of Figure 15, no data structure is required by the evaluator, only unary Scheme functions are used. 31 (define eval-cps (lambda (exp env) (cond ((variable? exp) (lambda (cont) (cont (env exp)))) ((constant? exp) (lambda (cont) (cont exp))) ((lambda? exp) (lambda (cont) (cont (lambda (arg) (lambda (c) ((eval-cps (lambda->body exp) (extend env (lambda->parameter exp) arg)) c)))))) ((applic? exp) (lambda (cont) ((eval-cps (applic->operator exp) env) (lambda (fct) ((eval-cps (applic->operand exp) env) (lambda (arg) ((fct arg) cont))))))) (else (error "eval-cps: Unknown expression" exp))))) Figure 15: Ready to be transformed continuation-passing style evaluator So far, we have designed an evaluator for terms of the v-calculus that has the following property. For any program M 2 , evalv(M) is de ned if and only if ((eval-cps M the-init-env) the-init-cont) returns a semantic value. As explained by Friedman, Wand, and Haynes [16], the evaluation process can be divided into several tasks: 1. dispatching on the type of the expression to evaluate, 2. calling the evaluator recursively, 3. de ning continuations and functions, 4. passing values to continuations, and 5. returning the nal result to the initial continuation. We can distinguish the static from the dynamic tasks. The former can be performed at \compile time", while the latter must be performed at \run time". Typically, the tasks 1 to 3 are static, while the tasks 4 and 5 are dynamic. 7.0.3 Generation of Code Instead of Evaluation In the third stage, using Friedman, Wand, and Haynes's technique [16], we transform the evaluator into a translator by executing only the static tasks and leaving the dynamic tasks undone. The call-by-value lambda-calculus models call-by-value languages, such as Scheme. Thus, an abstraction of behaves similarly as a Scheme function. In order to translate 32 a term to continuation-passing style, we generate a term of that has exactly the same behaviour as the evaluator. Instead of an evaluation function eval-cps with the signature (.52) repeated below, eval-cps : (exp env) ! (cont ! semantic-value); we de ne, in Program .42, a translation function trad with the following signature trad : (exp env) ! cps term; (.53) where a \cps term" is a term of . Program .42 The function trad, displayed in Figure 16, translates an expression of to continuation-passing style. It is easily derived from the program of Figure 15. Each (lambda (cont) ...) is transformed as follows. Each Scheme variable is replaced by (the construction of) a variable of . Each Scheme application is replaced by (the construction of) an application of . Each Scheme function is replaced by (the construction of) an abstraction of . Notice that the bound variables cont, c, arg, and fct, introduced in the cps translation, have a fresh name (generated by gensym). Moreover, the variable arg in the translation of an abstraction has to be fresh for each of its occurrences as a parameter. 2 The function trad executes the rst three tasks of the evaluator: it dispatches on the type of the expression to translate, it is called recursively on each subexpression, and it generates the de nition of all functions and continuations. If the generated cps term is regarded as a program, then it has the following signature; it takes a continuation and returns a semantic value. cps term : cont ! semantic-value (.54) cont : semantic-value ! semantic-value semantic-value : semantic-value ! (cont ! semantic-value) j constant We have succeeded in our derivation of a translator to cps. Still, it can be improved. Indeed, in Figure 16, the environment env is used to rename all the variables introduced in the source term. (In order to preserve the lexical scope, we had to generate a fresh name for every parameter of the source term.) This renaming can be avoided by preserving in the cps term the same variables as in the source term. Program .43 The code of the translator to cps without environment appears in Figure 17. Only four fresh variables should be generated: cont, c, fct, and arg. 2 7.0.4 Formal De nition of the CPS Translation We shall use the cps translation as a proof technique. Unfortunately, it is not easy to reason about the translator of Figure 17 because the code is long and the notation is inappropriate. It is convenient to condense the program of Figure 17 into the new notation of De nition .44. 33 ;; using gensym, these symbols are guaranteed to be unique (define cont (make-variable (gensym 'cont))) (define c (make-variable (gensym 'c))) (define fct (make-variable (gensym 'fct))) (define arg (make-variable (gensym 'arg))) (define trad (lambda (exp env) (cond ((variable? exp) (make-lambda cont (make-applic cont (env exp)))) ((constant? exp) (make-lambda cont (make-applic cont exp))) ((lambda? exp) (let ((arg (make-variable (gensym 'arg)))) (make-lambda cont (make-applic cont (make-lambda arg (make-lambda c (make-applic (trad (lambda->body exp) (extend env (lambda->parameter exp) arg)) c))))))) ((applic? exp) (make-lambda cont (make-applic (trad (applic->operator exp) env) (make-lambda fct (make-applic (trad (applic->operand exp) env) (make-lambda arg (make-applic (make-applic fct arg) cont))))))) (else (error "trad: Unknown expression" exp))))) (define empty-env (lambda (x) x)) Figure 16: CPS translation (with environment) 34 ;; using gensym, these symbols are guaranteed to be unique (define cont (make-variable (gensym 'cont))) (define c (make-variable (gensym 'c))) (define fct (make-variable (gensym 'fct))) (define arg (make-variable (gensym 'arg))) (define trad (lambda (exp) (cond ((or (variable? exp) (constant? exp)) (make-lambda cont (make-applic cont exp))) ((lambda? exp) (make-lambda cont (make-applic cont (make-lambda (lambda->parameter exp) (make-lambda c (make-applic (trad (lambda->body exp)) c)))))) ((applic? exp) (make-lambda cont (make-applic (trad (applic->operator exp)) (make-lambda fct (make-applic (trad (applic->operand exp)) (make-lambda arg (make-applic (make-applic fct arg) cont))))))) (else (error "trad: Unknown expression" exp))))) Figure 17: CPS translation (without environment) De nition .44 [[x]] = :( x) if x is a variable or constant [[ x:M ]] = :( ( x: c:([[M ]]c))) [[(MN)]] = :([[M ]]( vm:([[N ]]( vn:((vm vn) ))))) 2 The cps translation consists of a set of translation rules having the following pattern: [[term]] = exp. The left-hand side of a rule is a term of in brackets, and the right-hand side is an expression of . Such a rule should be read as \the text of the cps translation of term is exp, in which every occurrence of [[e]] must be replaced by the text of the translation of e, and each newly introduced variable in exp is supposed not to collide with existing ones". Here again, we use the same hygienic convention about variables as in Section 1; this avoids the gensym calls of Figure 17. Each right-hand side of De nition .44 is obtained from the code of Figure 17. A cont variable is written , c is written c, fct is written vm, and arg is written vn. The resemblance between Figure 15 and De nition .44 might be more striking, but it should be remembered that De nition .44 de nes a translation, while the Figure 15 displays an evaluator. With the conventions that x:( y:M) can be written xy:M , and ((a b) c) can be written (a b c), we obtain Plotkin's cps translation [29]. De nition .45 (CPS translation) [[x]] = : x if x is a variable or a constant (.55) 35 [[ x:M ]] = : ( xc:[[M ]] c) (.56) [[(MN)]] = :[[M ]]( vm:[[N ]] ( vn:vm vn )) (.57) 2 The translation of a term of is an abstraction that is expected to be passed a continuation. When the translation of a term is applied to a continuation, the parameter is bound to this continuation; hence, we shall simply say that is the continuation of the term. Rules (.55) and (.56) translate values. In (.55), the continuation is applied to x, which represents, in the cps term, a variable (or constant) of the source term. Similarly, in (.56), is applied to ( xc:[[M ]]c), which represents in the cps term an abstraction of the source term. It should be observed that an abstraction x:M is translated to ( xc:[[M ]]c); each abstraction is given an extra argument (called a continuation) to which the result of the abstraction is passed. Since abstractions require two arguments in the cps term, the calling mechanism should be changed. This appears in rule (.57) where the translation of M is applied to the term ( vm:[[N ]]( vn:vmvn )); which is said to be the continuation of M . This continuation will be passed the value of M , and vm will be bound to it. Then, it applies the translation of N to the term ( vn:vmvn ); which is said to be the continuation of N . This continuation will be passed the value of N , and vn will be bound to it. Then, vm is applied to vn and , the continuation of (MN). Since M is the operator, the value of M is expected to be a function, and we see that, in the cps translation, the value vm of the operator is invoked on a value and a continuation. 7.0.5 Evaluating CPS Terms Let us go back to the problem of evaluating an expression of . The evaluation can proceed in two phases. First, a term of can be translated to continuation-passing style; second, the cps term can be evaluated [15]. Let us examine the right-hand sides of the rules of De nition .45. We see that there exist two di erent kinds of applications. 1. A redex of type 1 is the application of an abstraction (or functional constant) to a value and a continuation; this kind of application, which is of the form ((vmvn) ), corresponds to an application in the source term. 2. A redex of type 2 is the application of a value to a value; this is the case of all other applications. These are the only cases that can appear. Furthermore, they always appear in terminal position. Consequently, the search for a redex is improved since the redex is always at the top level. Hence, no stack is required to evaluate such an expression. This observation yields Program .46. 36 Program .46 The code of an evaluator for cps terms appears in Figure 18. The function eval-one detects redices of the rst or second kind, and performs either two or one reductions respectively. The function eval* is the re exive, transitive closure of eval-one. 2 Flanagan, Sabry, Duba, and Felleisen's na ve CPS abstract machine CcpsE [15] generalises the evaluator of Figure 18 with n-ary functions, conditionals, and blocks. Example .47 Let us again consider the term (( k:( 10 (k 5))) ( u:u)): Its cps translation appears in term (1) of Figure 19. We notice immediately the high number of abstractions. It results from the cps translation itself, which yields one abstraction for each variable of the source term, and three abstractions for each abstraction or application of the source term. In the cps term, subscripted variables are the new variables introduced by the cps translation, and unsubscripted variables are the one existing in the direct term. In Figure 19, the cps translation of ( u:u) appears in a gray box, while the cps translation of ( k:( 10 (k 5))) appears in a white box. Then, we begin the evaluation according to the algorithm of Figure 18 with an initial continuation K, which is the identity function. Since many reductions are required to reach a nal value, we display only some of the intermediate terms. For instance, term (2) is obtained by a v-reduction of the top level redex of term (1). (The top level redex is of type 2.). Term (2) is an application of the translation of ( k: : : :) to the translation of ( u:u) and the initial continuation. Term (2) is a redex of type 1. In term (4), the constant is applied to 10. Notice that functional constants also follow the calling convention of the continuation-passing style: a functional constant expects a constant and a continuation. Therefore, we introduce a new -rule for cps terms.(f v) ! ( : ( (f; v))) ( cps) According to rule cps, the application of a functional constant f to a constant v yields an abstraction that expects a continuation , which will be passed the result of the function for f and v. Amongst the numerous terms produced by the evaluation, we display the terms that are a redex of type 1, i.e. where a function is both applied to a value and a continuation. In fact, these terms correspond to redices in the source term. Indeed, an exact comparison between redices in the standard evaluation of Example .22 and redices in Figure 19 is displayed in the following table. Example .22 page 11 (.25) (.26) (.27) (.28) Figure 19 page 39 (2) (4) (6) (7) For instance, the redex in line (.27) consists of the application of ( u:u) to the value 5. The corresponding term in Figure 19 appears in line (6), which is a redex of type 1: it applies the translation of ( u:u) to the value 5 and the current continuation. 2 37 (define-record constant (val)) (define-record variable (var)) (define-record applic (operator operand)) (define-record lambda (parameter body)) (define value? (lambda (exp) (or (lambda? exp) (variable? exp) (constant? exp)))) (define beta-redex? (lambda (exp) (and (applic? exp) (lambda? (applic->operator exp) (value? (applic->operand exp)))))) (define delta-redex? (lambda (exp) (and (applic? exp) (constant? (applic->operator exp)) (constant? (applic->operand exp)) (delta-defined? (applic->operator exp) (applic->operand exp))))) (define redex1? (lambda (exp) (and (applic? exp) (applic? (applic->operator exp))))) (define redex2? (lambda (exp) (or (beta-redex? exp) (delta-redex? exp)))) (define c/init (make-lambda (make-variable 'x) (make-variable 'x))) (define eval-one (lambda (exp) (cond ((value? exp) #f) ((redex1? exp) (reduce (reduce (applic->operator (applic->operator exp)) (applic->operand (applic->operator exp))) (applic->operand exp))) ((redex2? exp) (reduce (applic->operator exp) (applic->operand exp))) (else (error "eval-one: unknown exp" exp))))) (define eval* (lambda (exp) (let ((result (eval-one exp))) (if result (eval* result) exp)))) --> to evaluate: (eval* (translate c/init)) Figure 18: Evaluation of CPS Terms 38 ( ( 1:( ( 2:(( 1 2)K)) ( u:( 3:( 3u))) )) ( k:( 4:( ( 9:( ( 10:(( 9 10)( 5:( ( 7:( ( 8:( ( 7 8) ( 6:(( 5 6) 4)) )) 5)) k)))) 10)) )))) (1) ! ( ( ( k:( 4:( ( 9:( ( 10:(( 9 10)( 5:( ( 7:( ( 8:( ( 7 8) ( 6:(( 5 6) 4)) )) 5)) k)))) 10)) ))) ( u:( 3:( 3u))) ) K) (2) ! ( ( 9:( ( 10:(( 9 10)( 5:( ( 7:( ( 8:( ( 7 8) ( 6:(( 5 6)K)) )) 5)) ( u:( 3:( 3u))) )))) 10)) ) (3) ! (( 10) ( 5:( ( 7:( ( 8:( ( 7 8) ( 6:(( 5 6)K)) )) 5)) ( u:( 3:( 3u))) ))) (4) ! ( ( 7:( ( 8:( ( 7 8) ( 6:(( 10 6)K)) )) 5)) ( u:( 3:( 3u))) ) (5) ! ( ( ( u:( 3:( 3u))) 5) ( 6:(( 10 6)K)) ) (6) ! (( 6:(( 10 6)K)) 5) (7) ! 50 (8) the cps translation of ( u:u) is in a gray box a functional constant also requires a continuation argument is applied to the translation of ( u:u) the initial continuation and the current continuation the translation of u:u is applied to 5 cps translation of ( k:( 10 (k 5))) the translation of ( k: : : :) and the initial continuation K Figure 19: Evaluation of the cps translation of (( k:( 10 (k 5))) ( u:u)) 39 Example .47 clearly shows that the cps translation introduces many abstractions, and hence many redices. Amongst the numerous reductions of the cps term, only four of them correspond to reductions in the direct term. The other reductions are usually called administrative reductions [29, 8, 33]. RemarkPartial evaluation is de ned as \a source-to-source program transformation technique for specialising programs with respect to parts of their input" [6]. This technique consists in removing the interpretative layer of any programwith respect to given inputs. As an application, the second Futamura projection [2] generates a compiler for a language from the de nition of an interpreter for this language. We can view the code of the translator of Figure 16 as the code generated by application of the second Futamura projection to the de nition of the interpreter of Figure 9. 2 8 First-Class Continuations 8.

برای دانلود رایگان متن کامل این مقاله و بیش از 32 میلیون مقاله دیگر ابتدا ثبت نام کنید

ثبت نام

اگر عضو سایت هستید لطفا وارد حساب کاربری خود شوید

منابع مشابه

Flexible Continuations in Logic Programs via Unfold/Fold Transformations and Goal Generalization

We consider the use of continuations for deriving e cient logic programs. It is known that both in the case of functional and logic programming, the introduction of continuations is a valuable technique for transforming old programs into new, more e cient ones. However, in general, in order to derive programs with high levels of e ciency, one should introduce continuations according to suitable...

متن کامل

On typing delimited continuations: three new solutions to the printf problem

In “Functional Unparsing” (JFP 8(6): 621–625, 1998), Danvy presented a type-safe printf function using continuations and an accumulator to achieve the effect of dependent types. The key technique employed in Danvy’s solution is the non-standard use of continuations: not all of its calls are tail calls, i.e., it uses delimited continuations. Against this backdrop, we present three new solutions ...

متن کامل

Wally Axiomatics of Branching Continuations

We give a brief introduction to the axiomatization of temporal logics. Branching continuations are shortly presented thereafter and the possibility of their clear syntactical axiomatization in a Hilbert-style system is investigated as last. Some basic preliminary observations and suggestions, how such axiomatization could start, are presented.

متن کامل

Programming with Tighter Control

This paper formalizes continuations as functions abstracting a delimited context instead of an unlimited one. When made available in an expression language such as Scheme, they provide a functional abstraction of control that can be used as any ordinary procedure. This approach sheds light on the applicative aspects of continuations by getting rid of their imperative part: jumping, and makes it...

متن کامل

(Prolegomena to a theory of) Completions, Continuations, and Coordination in Dialogue

1 Introduction Utterances such as 1.2, 1.3, and 2.2 in the following fragment of a transcript from the Bielefeld Toy Plan Corpus of task-oriented dia logues (Skuplik, 1999) are examples of COMPLETIONS (1.2, 2.2) and CONTINUATIONS (1.3). (1) 1.1 Inst So, jetzt nimmst Du [pause] Well, now you take 1.2 Cnst eine Schraube a screw 1.3 Inst eine <-> orangene mit einem Schlitz. an <-> orange one with ...

متن کامل

Capturing the Future by Replaying the Past

Delimited continuations are the mother of all monads! So goes the slogan inspired by Filinski’s 1994 paper, which showed that delimited continuations can implement any monadic e ect, letting the programmer use an e ect as easily as if it was built into the language. It’s a shame that not many languages have delimited continuations. Luckily, exceptions and state are also the mother of all monads...

متن کامل

ذخیره در منابع من


  با ذخیره ی این منبع در منابع من، دسترسی به آن را برای استفاده های بعدی آسان تر کنید

عنوان ژورنال:

دوره   شماره 

صفحات  -

تاریخ انتشار 2006